/**
 * Copyright © 2016, Forp Co., LTD
 * <p>
 * All Rights Reserved.
 */
package org.jeecg.common.datasource.jdbc;

import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.datasource.annotation.DBColumn;
import org.jeecg.common.datasource.annotation.DBTable;
import org.jeecg.common.datasource.jdbc.vo.ClassMapping;
import org.jeecg.common.datasource.jdbc.vo.DBMappingProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * JDBC同名规则O/R Mapping映射类<br/><br/>
 *          以Spring的BeanPropertyRowMapper类为基础，添加了以下功能优化：<br/>
 * 			(1) 添加Class cache功能，优化Class的属性和数据库字段对应的解析过程。<br/>
 * 			(2) 使用annotation机制来映射命名特殊的数据库字段。
 *
 * @author Bruce
 * @version 2016-4-1 18:03:07
 * @param <T>   业务类泛型
 */
public class BeanRowMapper<T> implements RowMapper<T> {
    /**
     * Logger
     */
    private final static Logger lg = LoggerFactory.getLogger(BeanRowMapper.class);

    /**
     * Class映射字段列表缓冲
     * 		key    - Class完整包路径
     * 		value - O/R mapping信息
     */
    private static final Map<String, ClassMapping> Mapping_Class_Cache = new HashMap<>();

    /**
     * Cache Lock
     */
    private static final ReentrantLock Lock = new ReentrantLock();

    /**
     * The class we are mapping to
     */
    private Class<T> mappedClass;
    /**
     * 当前查询结果集字段名称列表Cache
     * 		key    - 字段索引
     * 		value - 字段名称
     */
    private Map<Integer, String> rsmdFieldCache = new HashMap<>();
    /**
     * Whether we're strictly validating
     */
    private boolean checkFullyPopulated = false;
    /**
     * Whether we're defaulting primitives when mapping a null value
     */
    private boolean primitivesDefaultedForNullValue = false;

    public BeanRowMapper() {
    }

    /**
     * 构造方法
     *
     * @param clazz        the class that each row should be mapped to
     */
    public BeanRowMapper(Class<T> clazz) {
        this.mappedClass = clazz;
        initialize(mappedClass);
    }

    /**
     * 初始化指定Class的O/R Mapping信息
     *
     * @param clazz        要Mapping的类
     */
    private static void initialize(Class<?> clazz) {
        // 检测缓冲
        if (Mapping_Class_Cache.containsKey(clazz.getName())) {
            return;
        }

        Lock.lock();
        try {
            // Double check
            if (Mapping_Class_Cache.containsKey(clazz.getName())) {
                return;
            }

            // 映射规则
            ClassMapping mapping = new ClassMapping();

            // Table名称
            Annotation at;
            // Annotation文本
            // 默认为Class名称
            String atText = clazz.getSimpleName();
            // 解析Table annotation配置信息
            if (clazz.getAnnotations().length > 0) {
                at = clazz.getAnnotation(DBTable.class);
                if (at != null) {
                    atText = ((DBTable) at).name();
                    mapping.setDynamic(((DBTable) at).isDynamic());
                    mapping.setDynamicName(((DBTable) at).dynamicName());
                }
            }
            mapping.setTableName(atText);
            mapping.setSourceName(atText);
            lg.debug("");
            lg.debug(">>> {} --> {} <<<", clazz.getName(), mapping.getTableName());

            // Table字段
            Field field = null;
            DBColumn col = null;
            PropertyDescriptor pkProperty = null;
            PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(clazz);
            for (PropertyDescriptor pd : pds) {
                if (null == pd.getWriteMethod() || null == pd.getReadMethod()) {
                    continue;
                }
				// 默认为当前Field名称
                atText = pd.getName();
                DBMappingProperty pp = new DBMappingProperty();
                try {
                    // 解析annotation信息
                    field = getObjectField(clazz, pd.getName());
                    at = field.getAnnotation(DBColumn.class);
                    if (at != null) {
                        col = (DBColumn) at;
                        if(StringUtils.isNotBlank(col.name())){
                            atText = col.name();
                            pp.setDBField(true);

                        }
                        // 主键
						if(pd.getName().equalsIgnoreCase("id")){
							pkProperty = pd;
						}
                        if (col.isPrimaryKey()) {
                            pp.setDBField(true);
                            mapping.setPkType(pd.getPropertyType());
                            mapping.setPkProperty(pd.getName());
                            mapping.setPkColumn(atText.toUpperCase());
                        }
                    }
                } catch (Exception e) {
                    lg.error("{}的o/r mapping annotation信息获取失败：{}", pd.getName(), e.getMessage());
                }

                // 数据库字段名称 --> VO属性
                lg.debug(pd.getName() + " -> " + StringUtils.capitalize(atText));
                pp.setName(pd.getName());
                pp.setType(pd.getPropertyType());
                mapping.addProperty(atText.toUpperCase(), pp);
            }
			if(pkProperty != null && mapping.getPkType() == null){
				mapping.setPkType(pkProperty.getPropertyType());
			}
            lg.debug("Primary Key：{}", mapping.getPkColumn());
            // 加入Cache
            Mapping_Class_Cache.put(clazz.getName(), mapping);
        } finally {
            Lock.unlock();
        }
    }

    /**
     * 获取指定类的Mapping对应关系
     */
    public static ClassMapping getClassMapping(Class<?> clazz) {
        // 确保该Class Mapping信息已正确解析
        initialize(clazz);
        return Mapping_Class_Cache.get(clazz.getName());
    }

    /**
     * Extract the values for all columns in the current row（查询映射不限于DBColumn配置属性列表，不在配置范围内的属性根据名称相符原则进行处理）.
     *
     * <p>Utilizes public setters and result set metadata.
     * @see ResultSetMetaData
     */
    @Override
    @SuppressWarnings("unchecked")
    public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
        // 判断是否简单数据类型
        if (this.mappedClass.isAssignableFrom(String.class)) {
            String obj = rs.getString(1);
            return (T) obj;
        } else if (this.mappedClass.isAssignableFrom(Integer.class)) {
            Integer obj = rs.getInt(1);
            return (T) obj;
        } else if (this.mappedClass.isAssignableFrom(Long.class)) {
            Long obj = rs.getLong(1);
            return (T) obj;
        } else if (this.mappedClass.isAssignableFrom(BigDecimal.class)) {
            BigDecimal obj = rs.getBigDecimal(1);
            return (T) obj;
        }

        // 实例化VO对象
        T rowObject = BeanUtils.instantiate(this.mappedClass);
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(rowObject);

        // Class Mapping信息
        ClassMapping mappingRole = Mapping_Class_Cache.get(mappedClass.getName());

        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();

        // VO属性名称
        String columnName, propertyName;
        Class<?> propertyClass;
        DBMappingProperty dbMP;
        PropertyDescriptor pd;
        // 普通非save和update映射属性
        for (int index = 1; index <= columnCount; index++) {
            // 获取字段名称
            if (rsmdFieldCache.containsKey(index)) {
                columnName = rsmdFieldCache.get(index);
            } else {
                columnName = JdbcUtils.lookupColumnName(rsmd, index).toUpperCase();
                rsmdFieldCache.put(index, columnName);
            }

            // 根据Maping规则组织对象属性
            if (mappingRole.hasProperty(columnName)) {
                dbMP = mappingRole.getProperty(columnName);
                propertyName = dbMP.getName();
                propertyClass = dbMP.getType();
            } else {
                // 继续下一个属性
                continue;
            }

            try {
                // 获取column值
                Object value = getColumnValue(rs, index, propertyClass);
                // 设置VO属性
                bw.setPropertyValue(propertyName, value);
            } catch (NotWritablePropertyException ex) {
                throw new DataRetrievalFailureException("Unable to map column " + columnName + " to property " + propertyName, ex);
            }
        }

        return rowObject;
    }

    /**
     * Retrieve a JDBC object value for the specified column.
     * <p>The default implementation calls
     * {@link JdbcUtils#getResultSetValue(ResultSet, int, Class)}.
     * Subclasses may override this to check specific value types upfront,
     * or to post-process values return from <code>getResultSetValue</code>.
     *
     * @param rs                The ResultSet of holding the data
     * @param index                The column index
     * @param propertyType        The bean property that each result object is expected to match (or <code>null</code> if none specified)
     *
     * @return the Object value
     *
     * @throws SQLException in case of extraction failure
     * @see org.springframework.jdbc.support.JdbcUtils#getResultSetValue(ResultSet, int, Class)
     */
    private Object getColumnValue(ResultSet rs, int index, Class<?> propertyType) throws SQLException {
        return JdbcUtils.getResultSetValue(rs, index, propertyType);
    }

    /**
     * Set whether we're strictly validating that all bean properties have been
     * mapped from corresponding database fields.
     * <p>Default is <code>false</code>, accepting unpopulated properties in the
     * target bean.
     */
    public void setCheckFullyPopulated(boolean checkFullyPopulated) {
        this.checkFullyPopulated = checkFullyPopulated;
    }

    /**
     * Return whether we're strictly validating that all bean properties have been
     * mapped from corresponding database fields.
     */
    public boolean isCheckFullyPopulated() {
        return this.checkFullyPopulated;
    }

    /**
     * Set whether we're defaulting Java primitives in the case of mapping a null value from corresponding database fields.
     *
     * <p>Default is <code>false</code>, throwing an exception when nulls are mapped to Java primitives.
     */
    public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
        this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
    }

    /**
     * Return whether we're defaulting Java primitives in the case of mapping a null value from corresponding database fields.
     */
    public boolean isPrimitivesDefaultedForNullValue() {
        return primitivesDefaultedForNullValue;
    }

    public static Field getObjectField(Class<?> cls, String fieldName) {
        Field field = null;
        try {
            field = cls.getDeclaredField(fieldName);
        } catch (Exception e) {
            if (cls.getSuperclass() != null) {
                field = getObjectField(cls.getSuperclass(), fieldName);
            }
        }
        return field;
    }
}